﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.ComponentModel;
using System.Threading;
using FlickrNet;

namespace Juggler
{
    [DefaultPropertyAttribute("SizeMode")]
    internal class FlickRCrawler : Crawler
    {
        private const int PERPAGE = 100;
        private const double FLEXRATIOCONSTAINT = .15;
        private const double FLEXSIZECONSTRAINT = .20;
        private const PhotoSearchExtras SearchExtras = PhotoSearchExtras.OriginalDimensions;
        private int _NumberOfThreads;
        private Flickr FlickRClient;

        //Starts download process of FlickR Photos
        //ScreenResolution - Requested Screen Resolution to use
        //SaveLocation - Directory where images will be saved
        //NumberOfThreads - Number of threads to use for downloading
        //Callback - A callback to the download form to show status
        public override void StartDownloading(Resolution ScreenResolution, string SaveLocation, int NumberOfThreads,
                                              Action<UpdateData> Callback)
        {
            _CrawlStatus = Juggler.CrawlStatus.Downloading;

            //Sets the maximum threads in the thread pool 
            _NumberOfThreads = NumberOfThreads;
            ThreadPool.SetMaxThreads(NumberOfThreads, NumberOfThreads);

            _ScreenResolution = new Resolution(ScreenResolution.Width, ScreenResolution.Height,
                                               ScreenResolution.DisplayText, ScreenResolution.UrlPart,
                                               ScreenResolution.ResolutionType);
            _ScreenRatio = (double) _ScreenResolution.Width/(double) _ScreenResolution.Height;
            _SaveLocation = SaveLocation;
            _Callback = Callback;

            //Start the worker thread to begin gathering new images from FlickR
            ThreadPool.QueueUserWorkItem(new WaitCallback(BuildSearch));
        }

        //Builds and begins search
        private void BuildSearch(Object stateInfo)
        {
            //Requires a text search for Tag and Text Search Modes
            if ((string.IsNullOrEmpty(SearchText)) &&
                ((DownloadMode == FlickRDownloadingMode.Tag) || (DownloadMode == FlickRDownloadingMode.Text)))
            {
                _Callback(new UpdateData(EventType.Completed, "No Search Text Found."));
                _CrawlStatus = Juggler.CrawlStatus.Stopped;
                return;
            }


            _Callback(new UpdateData(EventType.Start, "Started Downoading"));
            PhotoSearchOptions FlickRSearchOptions = GetSearchOptions();

            StartSearch(GetSearchOptions());
        }

        //Provides Search Options based user options
        private PhotoSearchOptions GetSearchOptions()
        {
            PhotoSearchOptions FlickRSearchOptions = new PhotoSearchOptions
                                                         {
                                                             PerPage = PERPAGE,
                                                             Extras = SearchExtras,
                                                             SortOrder = PhotoSearchSortOrder.InterestingnessDesc
                                                         };
            switch (DownloadMode)
            {
                case FlickRDownloadingMode.Generic:
                    FlickRSearchOptions.Tags = "wallpaper";
                    break;
                case FlickRDownloadingMode.Tag:
                    FlickRSearchOptions.Tags = SearchText;
                    break;
                case FlickRDownloadingMode.Text:
                    FlickRSearchOptions.Text = SearchText;
                    break;
                case FlickRDownloadingMode.Popular:
                    //Nothing to do curently
                    break;
            }
            return FlickRSearchOptions;
        }

        //Searches through all the photos for photos
        //FlickRSearchOption - Search Options from User
        private void StartSearch(PhotoSearchOptions FlickRSearchOptions)
        {
            try
            {
                FlickRClient = new Flickr(Juggler.Properties.Settings.Default.APIKey);
                Photos PhotoResults;
                int Page = 1;

                //loops for each page in photo results.
                do
                {
                    //For just popular photos we can't use the PhotoSearchOptions so we handle it here.
                    if (DownloadMode == FlickRDownloadingMode.Popular)
                        PhotoResults = FlickRClient.InterestingnessGetList(SearchExtras, PERPAGE, Page);
                    else
                    {
                        //Gets page of search results
                        FlickRSearchOptions.Page = Page;
                        
                        //Retrieves results.  An XML Error occurred once during initial testing.  Hasn't happened since.
                        PhotoResults = FlickRClient.PhotosSearch(FlickRSearchOptions);
                        
                    }

                    _Callback(new UpdateData(EventType.ItemUpdate,
                                             string.Format("Parsing Page {0} of {1}", Page, PhotoResults.TotalPages)));

                    //Parses each image
                    foreach (Photo CurrentPhoto in PhotoResults.PhotoCollection)
                    {
                        if (CurrentPhoto.OriginalWidth == -1) continue;
                        if (_CrawlStatus != Juggler.CrawlStatus.Downloading) return;
                        Size largestImage = GetLargestImage(FlickRClient.PhotosGetSizes(CurrentPhoto.PhotoId));
                        ParseImage(CurrentPhoto.Title, largestImage.Height,largestImage.Width,largestImage.Source);
                    }
                    Page++;
                } while (_CrawlStatus == Juggler.CrawlStatus.Downloading &&
                         PhotoResults.PageNumber < PhotoResults.TotalPages);
            }
            catch (FlickrApiException)
            {
                //If we lose connectivity to FlickR this exception is raised.
                //For now we are just stopping.  Later we should handle this by
                //remembering the page we were on and resuming.  Or auto-resuming.
                _Callback(new UpdateData(EventType.Completed, "FlickR Service Unavailable"));
                _CrawlStatus = Juggler.CrawlStatus.Stopped;
            }
        }


        //Returns Largest Image for size checking.
        //PhotoSizes - Collection of all the photo sizes for a given image
        private Size GetLargestImage(Sizes PhotoSizes)
        {
            int Height = PhotoSizes.SizeCollection.Max(p => p.Height);
            return PhotoSizes.SizeCollection.First(p => p.Height == Height);
        }


        //Checks if the image is suitable for downloading
        //imageName - Name of the image
        //Height - Height of the Image
        //Width - Width of the Image
        //URL - URL to download Image
        private void ParseImage(string imageName, int Height, int Width, string URL)
        {
            if (_CrawlStatus != Juggler.CrawlStatus.Downloading) return;

            switch (SizeMode)
            {
                case FlickRSizeChecking.Strict: //Exact Dimensions
                    if ((Height != _ScreenResolution.Height) || (Width != _ScreenResolution.Width))
                    {
                        _Callback(new UpdateData(EventType.ItemUpdate, imageName, ImageStatus.Skipped));
                        return;
                    }
                    break;
                case FlickRSizeChecking.Lazy: //All accepted
                    //Nothing to do currently
                    break;
                case FlickRSizeChecking.Flexible: //Verifies within constraints and then downloads
                    if (!FlexImageCompatible(Width, Height))
                    {
                        _Callback(new UpdateData(EventType.ItemUpdate, imageName, ImageStatus.Skipped));
                        return;
                    }
                    break;
            }

            //Wrap our URL and Name to send to the ThreadPool
            Dictionary<string, string> PhotoData = new Dictionary<string, string>();
            PhotoData["URL"] = URL;
            PhotoData["Name"] = imageName;
            _Callback(new UpdateData(EventType.ItemUpdate, imageName, ImageStatus.Started));
            ThreadPool.QueueUserWorkItem(new WaitCallback(DownloadImage), PhotoData);
        }

        //Returns true if the image dimensions provided are within acceptable constraints
        private bool FlexImageCompatible(int PhotoWidth, int PhotoHeight)
        {
            //ratio check.  Must be within Screen Ratio Constraint plus or minus
            double PhotoRatio = (double) PhotoWidth/(double) PhotoHeight;
            if ((PhotoRatio < _ScreenRatio - FLEXRATIOCONSTAINT) || (PhotoRatio > _ScreenRatio + FLEXRATIOCONSTAINT))
                return false;

            //size check.  Can be bigger, but can only be a percentage smaller.  Defined by Screen Picture Constraint
            if ((PhotoWidth < _ScreenResolution.Width) && (1 - PhotoWidth/_ScreenResolution.Width > FLEXSIZECONSTRAINT))
                return false;

            return true;
        }

        //Downloads the image
        private void DownloadImage(object objPhoto)
        {
            if (_CrawlStatus != Juggler.CrawlStatus.Downloading) return;

            Dictionary<string, string> PhotoData = (Dictionary<string, string>)objPhoto;
            string URL = PhotoData["URL"];
            string Name = PhotoData["Name"];
            
            Stream dataStream = GetWebStream(new Uri(URL));

            if (dataStream == null)
            {
                _Callback(new UpdateData(EventType.ItemUpdate, Name, ImageStatus.Skipped));
                return;
            }

            SaveImage(dataStream, _SaveLocation, Name, _Callback);

            _Callback(new UpdateData(EventType.ItemUpdate, Name, ImageStatus.Saved));
        }

        //Stops downloading
        public override void StopDownloading()
        {
            if (_CrawlStatus != Juggler.CrawlStatus.Downloading) return;

            _Callback(new UpdateData(EventType.Completed, "Downloading Stopping"));
            _CrawlStatus = Juggler.CrawlStatus.StopRequested;
        }

        //Provides crawler status.  Officially stopped if there are no working threads in the threadpool.
        public override CrawlStatus CrawlStatus()
        {
            if ((_CrawlStatus == Juggler.CrawlStatus.StopRequested) || (_CrawlStatus == Juggler.CrawlStatus.StopPending))
            {
                int workers, available;
                ThreadPool.GetAvailableThreads(out workers, out available);

                _CrawlStatus = workers+1 < _NumberOfThreads ? Juggler.CrawlStatus.StopPending : Juggler.CrawlStatus.Stopped;
            }

            return _CrawlStatus;
        }

        [CategoryAttribute("General"), DescriptionAttribute("Determines the Sizes of Photos that are downloaded.")]
        public FlickRSizeChecking SizeMode { get; set; }

        [CategoryAttribute("General"), DescriptionAttribute("Method for finding pictures on FlickR")]
        public FlickRDownloadingMode DownloadMode { get; set; }

        [CategoryAttribute("General"), DescriptionAttribute("Search Text (Tag and Text Modes Only)")]
        public string SearchText { get; set; }
    }
}